﻿using System;
using System.Configuration;
using System.Data.SqlClient;

namespace SessionHunter.DataAccess
{
    /// <summary>
    /// Manages the retrieval and persistance of the connection string used to get the session
    /// </summary>
    public class ConnectionStringRepository
    {
        private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

        #region Constants
        /// <summary>
        /// The key for the connection string to get session data out
        /// </summary>
        private const string _connectionStringName = "SessionDatabase";
        #endregion

        /// <summary>
        /// Saves the connection string values.
        /// </summary>
        /// <param name="dataSource">The server and instance.</param>
        /// <param name="database">The database.</param>
        /// <param name="isEncrypted">if set to <c>true</c> [is encrypted].</param>
        /// <param name="isIntegratedAuth">if set to <c>true</c> [is integrated auth].</param>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        public void SaveConnectionStringValues(string dataSource, string database, bool isEncrypted, bool isIntegratedAuth, string userName = null, string password = null)
        {
            if (isIntegratedAuth)
                logger.Debug("Saving Integrated auth connection {0},{1},{2}", dataSource, database, isEncrypted);
            else
                logger.Debug("Saving SQL auth connection {0},{1},{2},{3},{4}", dataSource, database, isEncrypted, userName, password);

            var connectionString = CreateConnectionString(dataSource, database, isIntegratedAuth, userName, password);

            // Write it to the config
            ApplyChangesToConfigFile(connectionString, isEncrypted);
        }

        /// <summary>
        /// Creates the connection string based on the inputs.
        /// </summary>
        /// <param name="dataSource">The server and instance.</param>
        /// <param name="database">The database.</param>
        /// <param name="isIntegratedAuth">if set to <c>true</c> [is integrated auth].</param>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <returns>
        /// A connection string that should be valid
        /// </returns>
        public string CreateConnectionString(string dataSource, string database, bool isIntegratedAuth, string userName = null, string password = null)
        {
            // Build the database string
            var builder = new SqlConnectionStringBuilder();
            builder.DataSource = dataSource;
            builder.InitialCatalog = database;
            if (!isIntegratedAuth)
            {
                builder.UserID = userName;
                builder.Password = password;
            }
            else
            {
                // Only set this when true, because false is the default
                builder.IntegratedSecurity = true;
            }
            return builder.ConnectionString;
        }

        /// <summary>
        /// Applies the changes to config file.  
        /// Note: The encryption is two way, but can only be decrypted on this machine.  It is not portable.
        /// </summary>
        /// <param name="connectionString">The connection string.</param>
        /// <param name="isEncrypted">if set to true, it will encrypt it for the local machine</param>
        protected internal virtual void ApplyChangesToConfigFile(string connectionString, bool isEncrypted)
        {
            logger.Info("Saving connection string.  Encrypted: " + isEncrypted.ToString());
            logger.Debug("Connection string: " + connectionString);

            var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            var connectionStringsSection = (ConnectionStringsSection)config.GetSection("connectionStrings");

            var connectionStringSetting = connectionStringsSection.ConnectionStrings[_connectionStringName];
            if (connectionStringSetting == null)
            {
                // Add new connection string
                connectionStringsSection.ConnectionStrings.Add(new ConnectionStringSettings(_connectionStringName, connectionString));
            }
            else
            {
                // Update existing connection string
                connectionStringsSection.ConnectionStrings[_connectionStringName].ConnectionString = connectionString;
            }

            if (isEncrypted)
            {
                // Encrypt the section.
                connectionStringsSection.SectionInformation.ProtectSection("DataProtectionConfigurationProvider");
            }
            else if (connectionStringsSection.SectionInformation.IsProtected)
            {
                // Remove encryption if we want to clear it.
                connectionStringsSection.SectionInformation.UnprotectSection();
            }

            config.Save();
            ConfigurationManager.RefreshSection("connectionStrings");
        }

        /// <summary>
        /// Gets the elements that make up the connection string.  If the server is null, then it has never been set.
        /// </summary>
        /// <param name="dataSource">The server and instance.</param>
        /// <param name="database">The database.</param>
        /// <param name="isEncrypted">if set to <c>true</c> [is encrypted].</param>
        /// <param name="isIntegratedAuth">if set to <c>true</c> [is integrated auth].</param>
        /// <param name="userName">Username.</param>
        public void GetConnectionStringValues(out string dataSource, out string database, out bool isEncrypted, out bool isIntegratedAuth, out string userName)
        {
            dataSource = null;
            database = null;
            isEncrypted = false;
            isIntegratedAuth = false;
            userName = null;

            var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            var connectionStringsSection = (ConnectionStringsSection)config.GetSection("connectionStrings");
            isEncrypted = connectionStringsSection.SectionInformation.IsProtected;
            var connectionString = GetConnectionString();
            if (String.IsNullOrEmpty(connectionString))
                return;

            var builder = new SqlConnectionStringBuilder(connectionString);
            dataSource = builder.DataSource;
            database = builder.InitialCatalog;
            isIntegratedAuth = builder.IntegratedSecurity;
            if (!isIntegratedAuth)
            {
                userName = builder.UserID;
                // Note: We don't return the password, because we need the user to re-enter it
            }
        }

        /// <summary>
        /// Gets the connection string from configuration.
        /// </summary>
        /// <returns>String or null if not found</returns>
        public virtual string GetConnectionString()
        {
            logger.Info("Reading connection string " + _connectionStringName);

            var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            var connectionStringsSection = (ConnectionStringsSection)config.GetSection("connectionStrings");
            if (connectionStringsSection.SectionInformation.IsProtected)
            {
                // Remove encryption so we can read it.
                connectionStringsSection.SectionInformation.UnprotectSection();
            }

            var connectionStringSetting = connectionStringsSection.ConnectionStrings[_connectionStringName];
            if (connectionStringSetting == null)
            {
                logger.Info("Connection String " + _connectionStringName + " not found in config file");
                return null;
            }
            else
            {
                var connectionString = connectionStringSetting.ConnectionString;
                logger.Debug("Connection String: " + connectionString);
                return connectionString;
            }
        }
    }
}
